// FindTaskExpressionListCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "todolist.h"
#include "FindTaskExpressionListCtrl.h"

#include "..\shared\HoldRedraw.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

enum { ATTRIB, OPERATOR, VALUE, ANDOR };
enum { ATTRIB_ID = 5000, OPERATOR_ID, ANDOR_ID, DATE_ID, TIME_ID };

const float COL_PROPORTIONS[] = { 12.0f/47, 16.0f/47, 13.0f/47, 6.0f/47 };

struct ATTRIB_PAIR
{
	TDC_ATTRIBUTE attrib;
	UINT nAttribResID;
};

struct OP_PAIR
{
	FIND_OPERATOR op;
	UINT nOpResID;
};

static ATTRIB_PAIR ATTRIBUTES[] = 
{
	{ TDCA_NONE, 0 },
	{ TDCA_TASKNAME, IDS_TDLBC_TITLE },
	{ TDCA_DONEDATE, IDS_TDLBC_DONEDATE },
	{ TDCA_DUEDATE, IDS_TDLBC_DUEDATE },
	{ TDCA_STARTDATE, IDS_TDLBC_STARTDATE },
	{ TDCA_PRIORITY, IDS_TDLBC_PRIORITY },
//	{ TDCA_COLOR, IDS_TDLBC_COLOR },
	{ TDCA_ALLOCTO, IDS_TDLBC_ALLOCTO },
	{ TDCA_ALLOCBY, IDS_TDLBC_ALLOCBY },
	{ TDCA_STATUS, IDS_TDLBC_STATUS },
	{ TDCA_CATEGORY, IDS_TDLBC_CATEGORY },
	{ TDCA_PERCENT, IDS_TDLBC_PERCENT },
	{ TDCA_TIMEEST, IDS_TDLBC_TIMEEST },
	{ TDCA_TIMESPENT, IDS_TDLBC_TIMESPENT },
	{ TDCA_FILEREF, IDS_TDLBC_FILEREF },
//	{ TDCA_PROJNAME, IDS_PROJNAME },
	{ TDCA_FLAG, IDS_TDLBC_FLAG },
	{ TDCA_CREATIONDATE, IDS_TDLBC_CREATEDATE },
	{ TDCA_CREATEDBY, IDS_TDLBC_CREATEDBY },
	{ TDCA_RISK, IDS_TDLBC_RISK },
	{ TDCA_EXTERNALID, IDS_TDLBC_EXTERNALID },
	{ TDCA_COST, IDS_TDLBC_COST },
	{ TDCA_DEPENDENCY, IDS_TDLBC_DEPENDS },
//	{ TDCA_RECURRENCE, IDS_RECURRENCE },
	{ TDCA_VERSION,	IDS_TDLBC_VERSION  },
//	{ TDCA_POSITION, IDS_POSITION },
	{ TDCA_ID, IDS_TDLBC_ID },
	{ TDCA_LASTMOD, IDS_TDLBC_MODIFYDATE },
	{ TDCA_COMMENTS, IDS_TDLBC_COMMENTS }
};
const int ATTRIB_COUNT = sizeof(ATTRIBUTES) / sizeof(ATTRIB_PAIR);

static OP_PAIR OPERATORS[] =
{
	{ FO_NONE, 0 },
	{ FO_EQUALS, IDS_FP_EQUALS },
	{ FO_NOT_EQUALS, IDS_FP_NOT_EQUALS },
	{ FO_INCLUDES, IDS_FP_INCLUDES },
	{ FO_NOT_INCLUDES, IDS_FP_NOT_INCLUDES },
	{ FO_ON_OR_BEFORE, IDS_FP_ON_OR_BEFORE },
	{ FO_BEFORE, IDS_FP_BEFORE },
	{ FO_ON_OR_AFTER, IDS_FP_ON_OR_AFTER },
	{ FO_AFTER, IDS_FP_AFTER },
	{ FO_GREATER_OR_EQUAL, IDS_FP_GREATER_OR_EQUAL },
	{ FO_GREATER, IDS_FP_GREATER },
	{ FO_LESS_OR_EQUAL, IDS_FP_LESS_OR_EQUAL },
	{ FO_LESS, IDS_FP_LESS },
	{ FO_SET, IDS_FP_SET },
	{ FO_NOT_SET, IDS_FP_NOT_SET }
};
const int OP_COUNT = sizeof(OPERATORS) / sizeof(OP_PAIR);

#define ADD_OP_2_COMBO(cb, op) { int i = cb->AddString(GetOpName(op)); cb->SetItemData(i, (DWORD)op); }

/////////////////////////////////////////////////////////////////////////////
// CFindTaskExpressionListCtrl

CFindTaskExpressionListCtrl::CFindTaskExpressionListCtrl()
{
	CString sPrompt;
	sPrompt.LoadString(IDS_FP_NEW_RULE);

	SetAutoRowPrompt(sPrompt);
}

CFindTaskExpressionListCtrl::~CFindTaskExpressionListCtrl()
{
}

BEGIN_MESSAGE_MAP(CFindTaskExpressionListCtrl, CInputListCtrl)
	//{{AFX_MSG_MAP(CFindTaskExpressionListCtrl)
	ON_WM_KILLFOCUS()
	ON_WM_SIZE()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
	ON_WM_MEASUREITEM_REFLECT()
	ON_CBN_CLOSEUP(ATTRIB_ID, OnAttribEditCancel)
	ON_CBN_SELENDCANCEL(ATTRIB_ID, OnAttribEditCancel)
	ON_CBN_SELENDOK(ATTRIB_ID, OnAttribEditOK)
	ON_CBN_SELENDCANCEL(OPERATOR_ID, OnOperatorEditCancel)
	ON_CBN_SELENDOK(OPERATOR_ID, OnOperatorEditOK)
	ON_CBN_SELENDCANCEL(ANDOR_ID, OnAndOrEditCancel)
	ON_CBN_SELENDOK(ANDOR_ID, OnAndOrEditOK)
	ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, OnValueEditOK)
	ON_NOTIFY_REFLECT_EX(LVN_ITEMCHANGED, OnSelItemChanged)
	ON_NOTIFY(DTN_DATETIMECHANGE, DATE_ID, OnDateChange)
	ON_NOTIFY(DTN_CLOSEUP, DATE_ID, OnDateCloseUp)
	ON_REGISTERED_MESSAGE(WM_TEN_UNITSCHANGE, OnTimeUnitsChange)
	ON_EN_CHANGE(TIME_ID, OnTimeChange)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFindTaskExpressionListCtrl message handlers

void CFindTaskExpressionListCtrl::PreSubclassWindow() 
{
	// create child controls
	CreateControl(m_cbAttributes, ATTRIB_ID);
	CreateControl(m_cbOperators, OPERATOR_ID, FALSE);
	CreateControl(m_cbAndOr, ANDOR_ID, FALSE);
	CreateControl(m_dtDate, DATE_ID);
	CreateControl(m_eTime, TIME_ID);

	CInputListCtrl::PreSubclassWindow(); // we need combo to be created first

	// build attrib combo because that is static
	for (int nAttrib = 0; nAttrib < ATTRIB_COUNT; nAttrib++)
	{
		const ATTRIB_PAIR& ap = ATTRIBUTES[nAttrib];

		if (ap.nAttribResID)
		{
			int nItem = m_cbAttributes.AddString(CEnString(ap.nAttribResID)); 
			m_cbAttributes.SetItemData(nItem, (DWORD)ap.attrib); 
		}
	}

	// build and/or combo too
	int nItem = m_cbAndOr.AddString(CEnString(IDS_FP_AND));
	m_cbAndOr.SetItemData(nItem, TRUE);
				
	nItem = m_cbAndOr.AddString(CEnString(IDS_FP_OR));
	m_cbAndOr.SetItemData(nItem, FALSE);

	// post message for our setup
	m_header.EnableTracking(FALSE);
	ShowGrid(TRUE, TRUE);

	InsertColumn(ATTRIB, "Attribute", LVCFMT_LEFT, 120);
	InsertColumn(OPERATOR, "Matches", LVCFMT_LEFT, 160);
	InsertColumn(VALUE, "Value", LVCFMT_LEFT, 130);
	InsertColumn(ANDOR, "And/or", LVCFMT_LEFT, 60);
	SetView(LVS_REPORT);
//	SetLastColumnStretchy(TRUE);

	AutoAdd(TRUE, FALSE);

	BuildListCtrl();

	SetColumnType(ATTRIB, DROPLIST);
	SetColumnType(OPERATOR, DROPLIST);
	SetColumnType(ANDOR, DROPLIST);

	ModifyStyle(WS_HSCROLL, 0);
}

void CFindTaskExpressionListCtrl::SetSearchParams(const SEARCHPARAM& param)
{
	m_aSearchParams.RemoveAll();
	m_aSearchParams.Add(param);

	if (GetSafeHwnd())
		BuildListCtrl();
}

void CFindTaskExpressionListCtrl::ClearSearch()
{
	m_aSearchParams.RemoveAll();

	if (GetSafeHwnd())
		BuildListCtrl();
}

void CFindTaskExpressionListCtrl::SetSearchParams(const CSearchParamArray& params)
{
	m_aSearchParams.Copy(params);

	if (GetSafeHwnd())
		BuildListCtrl();
}

int CFindTaskExpressionListCtrl::GetSearchParams(CSearchParamArray& params) const
{
	params.Copy(m_aSearchParams);

	return params.GetSize();
}

void CFindTaskExpressionListCtrl::CreateControl(CWnd& ctrl, UINT nID, BOOL bSort)
{
	DWORD dwStyle = WS_CHILD;

	if (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)))
	{
		dwStyle |= CBS_DROPDOWNLIST | WS_VSCROLL | CBS_AUTOHSCROLL;

		if (bSort)
			dwStyle |= CBS_SORT;

		CComboBox* pCombo = (CComboBox*)&ctrl;
		VERIFY (pCombo->Create(dwStyle, CRect(0, 0, 0, 0), this, nID));
	}
	else if (ctrl.IsKindOf(RUNTIME_CLASS(CEdit)))
	{
		CEdit* pEdit = (CEdit*)&ctrl;
		VERIFY (pEdit->Create(dwStyle, CRect(0, 0, 0, 0), this, nID));

		pEdit->ModifyStyleEx(0, WS_EX_CLIENTEDGE, 0);
	}
	else //if (ctrl.IsKindOf(RUNTIME_CLASS(CDateTimeCtrl)))
	{
		CDateTimeCtrl* pDateTime = (CDateTimeCtrl*)&ctrl;
		VERIFY (pDateTime->Create(dwStyle, CRect(0, 0, 0, 0), this, nID));
	}

	ctrl.SetFont(GetFont()); // set font to parents
}

void CFindTaskExpressionListCtrl::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	CRect rCombo;
	m_cbAttributes.GetWindowRect(rCombo);
	m_nItemHeight = rCombo.Height();
    
    lpMeasureItemStruct->itemHeight = m_nItemHeight - 1;
}

BOOL CFindTaskExpressionListCtrl::DeleteSelectedCell()
{
	int nRow, nCol;
	GetCurSel(nRow, nCol);

	if (nRow < GetRuleCount())
	{
		if (nCol == ATTRIB)
		{
			CInputListCtrl::DeleteSelectedCell();
			m_aSearchParams.RemoveAt(nRow);

			ValidateListData();
			SetCurSel(nRow);

			return TRUE;
		}
		else if (nCol == VALUE) // clear text
		{
			SetItemText(nRow, nCol, "");
			m_aSearchParams[nRow].sValue = "";
		}
	}
	
	// else
	return FALSE;
}

CWnd* CFindTaskExpressionListCtrl::GetEditControl(int nItem, int nCol)
{
	if (nItem < 0 || nItem > GetRuleCount() || nCol > ANDOR)
		return NULL;

	const SEARCHPARAM& sp = m_aSearchParams[nItem];

	switch (nCol)
	{
	case ATTRIB:
		return &m_cbAttributes;

	case OPERATOR:
		if (sp.attrib != TDCA_NONE)
			return &m_cbOperators;
		break;

	case VALUE:
		if (sp.op == FO_SET || sp.op == FO_NOT_SET)
		{
			// handled by operator
		}
		else
		{
			switch (sp.GetAttribType())
			{
			case FT_DATE:
				return &m_dtDate;

			case FT_TIME:
				return &m_eTime;

			case FT_BOOL:
				// do nothing: it's handled by the operator
				break;

			case FT_NONE:
				// do nothing.
				break;

			default:
				return CInputListCtrl::GetEditControl();
			}
		}
		break;

	case ANDOR:
		return &m_cbAndOr;
	}

	// all else
	return NULL;
}


void CFindTaskExpressionListCtrl::EditCell(int nItem, int nCol)
{
	// handle new rules
	if (nItem == GetRuleCount() && nCol == ATTRIB)
		AddRule();

	CWnd* pEdit = GetEditControl(nItem, nCol);
	ASSERT (pEdit);

	if (!pEdit)
		return;

	const SEARCHPARAM& sp = m_aSearchParams[nItem];

	switch (nCol)
	{
	case ATTRIB:
	case OPERATOR:
	case ANDOR:
		ShowControl(*pEdit, nItem, nCol);
		break;

	case VALUE:
		if (sp.op != FO_SET && sp.op != FO_NOT_SET)
		{
			switch (sp.GetAttribType())
			{
			case FT_DATE:
			case FT_TIME:
				ShowControl(*pEdit, nItem, nCol);
				break;

			default:
				PrepareEdit(nItem, nCol);
				CInputListCtrl::EditCell(nItem, nCol);
			}
		}
		break;
	}
}

void CFindTaskExpressionListCtrl::EndEdit()
{
	int nRow, nCol;
	GetCurSel(nRow, nCol);

	if (nRow == -1 || nCol == -1)
		return;

	// if any editing control is visible, just shift the
	// focus back to the list to end the edit
	if (IsEditing())
		SetFocus();
}

BOOL CFindTaskExpressionListCtrl::IsEditing() const
{
	return CInputListCtrl::IsEditing() ||
			m_cbOperators.IsWindowVisible() ||
			m_cbAndOr.IsWindowVisible() ||
			m_cbAttributes.IsWindowVisible() ||
			m_dtDate.IsWindowVisible() ||
			m_eTime.IsWindowVisible();
}

BOOL CFindTaskExpressionListCtrl::CanEditSelectedCell() const
{
	int nRow, nCol;
	GetCurSel(nRow, nCol);

	// Boolean type is a special case
	if (nRow < m_aSearchParams.GetSize())
	{
		const SEARCHPARAM& rule = m_aSearchParams[nRow];
		
		if (rule.GetAttribType() == FT_BOOL && nCol == VALUE)
			return FALSE;
	}

	// else
	return CInputListCtrl::CanEditSelectedCell();
}

void CFindTaskExpressionListCtrl::PrepareEdit(int nRow, int /*nCol*/)
{
	const SEARCHPARAM& sp = m_aSearchParams[nRow];

	switch (sp.GetAttribType())
	{
	case FT_STRING:
		m_editBox.SetMask("");
		break;
		
	case FT_INTEGER:
		m_editBox.SetMask("1234567890");
		break;
		
	case FT_DOUBLE:
		m_editBox.SetMask("1234567890.", ME_LOCALIZEDECIMAL);
		break;

	case FT_DATE:
	case FT_BOOL:
	case FT_NONE:
	default:
		ASSERT(0);
	}
}

BOOL CFindTaskExpressionListCtrl::AddRule()
{
	SEARCHPARAM sp(TDCA_TASKNAME, FO_INCLUDES);

	int nRow = InsertRule(GetRuleCount(), sp);

	// make sure the 'and/or' text of the preceding rule is set
	if (nRow > 0)
	{
		SEARCHPARAM& spPrev = m_aSearchParams[nRow - 1];
		CEnString sAndOr(spPrev.bAnd ? IDS_FP_AND : IDS_FP_OR);
		SetItemText(nRow - 1, ANDOR, sAndOr);
	}

	SetCurSel(nRow, ATTRIB);
	EnsureVisible(nRow, FALSE);

	return TRUE;
}

BOOL CFindTaskExpressionListCtrl::DeleteSelectedRule()
{
	int nRow = GetCurSel();

	if (nRow != -1 && CanDeleteSelectedCell())
	{
		DeleteItem(nRow);
		m_aSearchParams.RemoveAt(nRow);

		ValidateListData();

		SetCurSel(nRow);
		EnsureVisible(nRow, FALSE);

		return TRUE;
	}

	return FALSE;
}

void CFindTaskExpressionListCtrl::MoveSelectedRuleUp()
{
	if (CanMoveSelectedRuleUp())
	{
		int nRow, nCol;
		GetCurSel(nRow, nCol);

		// save off rule
		SEARCHPARAM sp = m_aSearchParams[nRow];

		// delete rule
		m_aSearchParams.RemoveAt(nRow);
		DeleteItem(nRow);

		// reinsert rule
		nRow = InsertRule(nRow - 1, sp);

		// sanity check
		ValidateListData();

		// restore selection
		SetCurSel(nRow, nCol);
		EnsureVisible(nRow, FALSE);
	}
}

int CFindTaskExpressionListCtrl::InsertRule(int nRow, const SEARCHPARAM& sp)
{
	m_aSearchParams.InsertAt(nRow, sp);

	int nNew = InsertItem(nRow, GetAttribName(sp.attrib));
	SetItemText(nNew, OPERATOR, GetOpName(sp.op));

	CString sValue;

	switch (sp.GetAttribType(sp.attrib))
	{
	case FT_STRING:
	case FT_INTEGER:
	case FT_DOUBLE:
		sValue = sp.ValueAsString();
		break;
		
	case FT_DATE:
		sValue = COleDateTime(sp.dValue).Format(VAR_DATEVALUEONLY);
		break;
		
	case FT_TIME:
		sValue = CTimeHelper::FormatTime(sp.dValue, sp.dwFlags, 2);
		break;
		
	case FT_BOOL:
		// handled by operator
		break;
	}
	SetItemText(nNew, VALUE, sValue);
	
	// omit and/or for last rule
	if (nRow < GetRuleCount() - 1)
	{
		CEnString sAndOr(sp.bAnd ? IDS_FP_AND : IDS_FP_OR);
		SetItemText(nNew, ANDOR, sAndOr);
	}

	return nNew;
}

BOOL CFindTaskExpressionListCtrl::CanMoveSelectedRuleUp() const
{
	int nRow = GetCurSel();

	return (nRow > 0 && nRow < GetRuleCount());
}

void CFindTaskExpressionListCtrl::MoveSelectedRuleDown()
{
	if (CanMoveSelectedRuleDown())
	{
		int nRow, nCol;
		GetCurSel(nRow, nCol);

		// save off rule
		SEARCHPARAM sp = m_aSearchParams[nRow];

		// delete rule
		m_aSearchParams.RemoveAt(nRow);
		DeleteItem(nRow);

		// reinsert rule
		nRow = InsertRule(nRow + 1, sp);
	
		// sanity check
		ValidateListData();

		// restore selection
		SetCurSel(nRow, nCol);
		EnsureVisible(nRow, FALSE);
	}
}

BOOL CFindTaskExpressionListCtrl::CanMoveSelectedRuleDown() const
{
	return (GetCurSel() < GetRuleCount() - 1);
}

void CFindTaskExpressionListCtrl::ShowControl(CWnd& ctrl, int nRow, int nCol)
{
	PrepareControl(ctrl, nRow, nCol);

	CRect rCell;
	GetCellEditRect(nRow, nCol, rCell);

	if (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)))
		rCell.bottom += 200;
//	else
//		rCell.bottom++;

	ctrl.MoveWindow(rCell);
	ctrl.EnableWindow(TRUE);
	ctrl.ShowWindow(SW_SHOW);
	ctrl.SetFocus();

	if (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)))
	{
		CComboBox* pCombo = (CComboBox*)&ctrl;
		pCombo->ShowDropDown(TRUE);
	}
	else //if (ctrl.IsKindOf(RUNTIME_CLASS(CDateTimeCtrl)))
	{
		//CDateTimeCtrl* pDateTime = (CDateTimeCtrl*)&ctrl;
	}
}

void CFindTaskExpressionListCtrl::PrepareControl(CWnd& ctrl, int nRow, int nCol)
{
	if (!GetRuleCount())
		return;

	SEARCHPARAM& sp = m_aSearchParams[nRow];

	switch (nCol)
	{
	case ATTRIB:
		{
			ASSERT (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)));
			CComboBox* pCombo = (CComboBox*)&ctrl;
	
			if (sp.GetAttribType() != FT_NONE)
				pCombo->SelectString(-1, GetAttribName(sp.attrib));
			else
				pCombo->SetCurSel(-1);
		}
		break;

	case OPERATOR:
		{
			ASSERT (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)));
			CComboBox* pCombo = (CComboBox*)&ctrl;

			pCombo->ResetContent();
			
			FIND_ATTRIBTYPE nType = sp.GetAttribType();

			switch (nType)
			{
			case FT_STRING:
				ADD_OP_2_COMBO(pCombo, FO_SET);
				ADD_OP_2_COMBO(pCombo, FO_NOT_SET);
				ADD_OP_2_COMBO(pCombo, FO_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_NOT_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_INCLUDES);
				ADD_OP_2_COMBO(pCombo, FO_NOT_INCLUDES);
				break;

			case FT_INTEGER:
			case FT_DOUBLE:
			case FT_TIME:
				ADD_OP_2_COMBO(pCombo, FO_SET);
				ADD_OP_2_COMBO(pCombo, FO_NOT_SET);
				ADD_OP_2_COMBO(pCombo, FO_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_NOT_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_GREATER);
				ADD_OP_2_COMBO(pCombo, FO_GREATER_OR_EQUAL);
				ADD_OP_2_COMBO(pCombo, FO_LESS);
				ADD_OP_2_COMBO(pCombo, FO_LESS_OR_EQUAL);
				break;

			case FT_DATE:
				ADD_OP_2_COMBO(pCombo, FO_SET);
				ADD_OP_2_COMBO(pCombo, FO_NOT_SET);
				ADD_OP_2_COMBO(pCombo, FO_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_NOT_EQUALS);
				ADD_OP_2_COMBO(pCombo, FO_AFTER);
				ADD_OP_2_COMBO(pCombo, FO_ON_OR_AFTER);
				ADD_OP_2_COMBO(pCombo, FO_BEFORE);
				ADD_OP_2_COMBO(pCombo, FO_ON_OR_BEFORE);
				break;

			case FT_BOOL:
				ADD_OP_2_COMBO(pCombo, FO_SET);
				ADD_OP_2_COMBO(pCombo, FO_NOT_SET);
				break;
			}
	
			if (sp.op != FO_NONE)
				pCombo->SelectString(-1, GetOpName(sp.op));
			else
				pCombo->SetCurSel(-1);
		}
		break;

	case ANDOR:
		{
			ASSERT (ctrl.IsKindOf(RUNTIME_CLASS(CComboBox)));
			CComboBox* pCombo = (CComboBox*)&ctrl;

			if (sp.bAnd)
				pCombo->SelectString(-1, CEnString(IDS_FP_AND));
			else
				pCombo->SelectString(-1, CEnString(IDS_FP_OR));
		}
		break;

	case VALUE:
		switch (sp.GetAttribType())
		{
		case FT_DATE:
			ASSERT (&ctrl == &m_dtDate);

			// if the rule does not yet have a date then set it now to
			// the current date because that's whats the date ctrl will default to
			if (sp.dValue <= 0)
			{
				sp.dValue = COleDateTime::GetCurrentTime();
				SetItemText(nRow, nCol, COleDateTime(sp.dValue).Format(VAR_DATEVALUEONLY));
			}
			else
				m_dtDate.SetTime(sp.dValue);
			break;

		case FT_TIME:
			ASSERT (&ctrl == &m_eTime);
			m_eTime.SetTime(sp.dValue, sp.dwFlags);
			break;
		}
		break;

	}
}

void CFindTaskExpressionListCtrl::ValidateListData() const
{
#ifdef _DEBUG
	for (int nRule = 0; nRule < GetRuleCount(); nRule++)
	{
		const SEARCHPARAM& rule = m_aSearchParams[nRule];

		// check matching attribute text 
		CString sRuleAttrib = GetAttribName(rule.attrib);
		CString sListAttrib = GetItemText(nRule, ATTRIB);
		ASSERT (sRuleAttrib == sListAttrib);

		// check matching operator text 
		CString sRuleOp = GetOpName(rule.op);
		CString sListOp = GetItemText(nRule, OPERATOR);
		ASSERT (sListOp.IsEmpty() || sRuleOp == sListOp);

		// check valid operator
		ASSERT(rule.HasValidOperator());
	}
#endif
}

void CFindTaskExpressionListCtrl::HideControl(CWnd& ctrl)
{
	if (ctrl.IsWindowVisible())
	{
		ctrl.ShowWindow(SW_HIDE);
		ctrl.EnableWindow(FALSE);
	}
}

void CFindTaskExpressionListCtrl::OnAttribEditCancel()
{
	HideControl(m_cbAttributes);
}

void CFindTaskExpressionListCtrl::OnAttribEditOK()
{
	HideControl(m_cbAttributes);

	// update attribute type
	int nSel = m_cbAttributes.GetCurSel();

	if (nSel != CB_ERR)
	{
		CString sSel;
		m_cbAttributes.GetLBText(nSel, sSel);

		int nRow = GetCurSel();
		SetItemText(nRow, ATTRIB, sSel);

		// keep data store synched
		TDC_ATTRIBUTE attrib = (TDC_ATTRIBUTE)m_cbAttributes.GetItemData(nSel);
		m_aSearchParams[nRow].SetAttribute(attrib);

		// clear the operator cell text if the operator was no longer valid
		if (m_aSearchParams[nRow].op == FO_NONE)
			SetItemText(nRow, OPERATOR, "");

		ValidateListData();
	}
}

BOOL CFindTaskExpressionListCtrl::OnSelItemChanged(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
{
//	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
//	int nRow = pNMListView->iItem;

	// always make sure we hide the datetime ctrl and the time edit
	HideControl(m_dtDate);
	HideControl(m_eTime);

	*pResult = 0;
	
	return FALSE; // continue routing
}

void CFindTaskExpressionListCtrl::OnValueEditOK(NMHDR* pNMHDR, LRESULT* pResult) 
{
	LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
	
	int nRow = pDispInfo->item.iItem;
	int nCol = pDispInfo->item.iSubItem;

	ASSERT (nCol == VALUE);

	SEARCHPARAM& sp = m_aSearchParams[nRow];
	
	switch (sp.GetAttribType())
	{
	case FT_STRING:
		sp.sValue = pDispInfo->item.pszText;
		break;
		
	case FT_INTEGER:
		sp.nValue = atoi(pDispInfo->item.pszText);
		break;
		
	case FT_DOUBLE:
		sp.dValue = atof(pDispInfo->item.pszText);
		break;
		
	case FT_DATE:
	case FT_BOOL:
		// not handled here
		break;
	}
		
	*pResult = 0;
}

void CFindTaskExpressionListCtrl::OnOperatorEditCancel()
{
	HideControl(m_cbOperators);
}

void CFindTaskExpressionListCtrl::OnOperatorEditOK()
{
	HideControl(m_cbOperators);

	// update operator type
	int nSel = m_cbOperators.GetCurSel();

	if (nSel != CB_ERR)
	{
		CString sSel;
		m_cbOperators.GetLBText(nSel, sSel);

		int nRow = GetCurSel();
		SetItemText(nRow, OPERATOR, sSel);

		// keep data store synched
		FIND_OPERATOR nOp = (FIND_OPERATOR)m_cbOperators.GetItemData(nSel);
		SEARCHPARAM& rule = m_aSearchParams[nRow];

		rule.SetOperator(nOp);

		// if the op is set/notset then clear the field
		if (nOp == FO_SET || nOp == FO_NOT_SET)
		{
			rule.dValue = 0.0;
			rule.nValue = 0;
			rule.sValue.Empty();

			SetItemText(nRow, VALUE, "");
		}

		ValidateListData();
	}
}

void CFindTaskExpressionListCtrl::OnAndOrEditCancel()
{
	HideControl(m_cbAndOr);
}

void CFindTaskExpressionListCtrl::OnAndOrEditOK()
{
	HideControl(m_cbAndOr);

	// update operator type
	int nSel = m_cbAndOr.GetCurSel();

	if (nSel != CB_ERR)
	{
		CString sSel;
		m_cbAndOr.GetLBText(nSel, sSel);

		int nRow = GetCurSel();
		SetItemText(nRow, ANDOR, sSel);

		// keep data store synched
		m_aSearchParams[nRow].bAnd = m_cbAndOr.GetItemData(nSel);

		ValidateListData();
	}
}

void CFindTaskExpressionListCtrl::BuildListCtrl()
{
	DeleteAllItems();

	for (int nParam = 0; nParam < GetRuleCount(); nParam++)
	{
		SEARCHPARAM& sp = m_aSearchParams[nParam];

		// attrib
		CString sAttrib = GetAttribName(sp.attrib);
		int nItem = InsertItem(nParam, sAttrib);

		// operator
		CString sOp = GetOpName(sp.op);
		SetItemText(nItem, OPERATOR, sOp);

		// and/or (but not for last row)
		if (nParam < GetRuleCount() - 1)
		{
			CEnString sAndOr(sp.bAnd ? IDS_FP_AND : IDS_FP_OR);
			SetItemText(nItem, ANDOR, sAndOr);
		}

		// value
		CString sValue;

		try
		{
			switch (sp.GetAttribType())
			{
			case FT_STRING:
			case FT_INTEGER:
			case FT_DOUBLE:
				sValue = sp.ValueAsString();
				break;
				
			case FT_DATE:
				if (sp.op == FO_SET || sp.op == FO_NOT_SET)
				{
					// handled by operator
				}
				else
					sValue = COleDateTime(sp.dValue).Format(VAR_DATEVALUEONLY);
				break;
				
			case FT_TIME:
				sValue = CTimeHelper::FormatTime(sp.dValue, sp.dwFlags, 2);
				break;
				
			case FT_BOOL:
				// handled by operator
				break;
			}

			SetItemText(nItem, VALUE, sValue);
		}
		catch (...)
		{
			// bad value but we continue
		}
	}

	ValidateListData();
	SetCurSel(0);
}

CString CFindTaskExpressionListCtrl::GetAttribName(TDC_ATTRIBUTE attrib)
{
	int nAttrib = ATTRIB_COUNT;

	while (nAttrib--)
	{
		if (ATTRIBUTES[nAttrib].attrib == attrib)
		{
			if (ATTRIBUTES[nAttrib].nAttribResID)
				return CEnString(ATTRIBUTES[nAttrib].nAttribResID);
			else
				return "";
		}
	}

	ASSERT (0); // not found
	return "";
}

CString CFindTaskExpressionListCtrl::GetOpName(FIND_OPERATOR op)
{
	int nOp = OP_COUNT;

	while (nOp--)
	{
		if (OPERATORS[nOp].op == op)
		{
			if (OPERATORS[nOp].nOpResID)
				return CEnString(OPERATORS[nOp].nOpResID);
			else
				return "";
		}
	}

	ASSERT (0); // not found
	return "";
}

void CFindTaskExpressionListCtrl::OnDateChange(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMDATETIMECHANGE pDTHDR = (LPNMDATETIMECHANGE)pNMHDR;
	COleDateTime dt(pDTHDR->st);

	// update the rule 
	int nRow = GetCurSel();

	m_aSearchParams[nRow].dValue = dt.m_dt;
	SetItemText(nRow, VALUE, dt.Format(VAR_DATEVALUEONLY));
	
	*pResult = 0;
}

void CFindTaskExpressionListCtrl::OnDateCloseUp(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
//	LPNMDATETIMECHANGE pDTHDR = (LPNMDATETIMECHANGE)pNMHDR;

	HideControl(m_dtDate);
	
	*pResult = 0;
}


void CFindTaskExpressionListCtrl::OnKillFocus(CWnd* pNewWnd) 
{
	CInputListCtrl::OnKillFocus(pNewWnd);
	
	if (pNewWnd != &m_dtDate)
		HideControl(m_dtDate);

	if (pNewWnd != &m_eTime)
		HideControl(m_eTime);
}

void CFindTaskExpressionListCtrl::OnTimeChange()
{
	// update the rule 
	int nRow = GetCurSel();
	SEARCHPARAM& rule = m_aSearchParams[nRow];

	rule.dValue = m_eTime.GetTime();
	rule.dwFlags = m_eTime.GetUnits();

	SetItemText(nRow, VALUE, CTimeHelper::FormatTime(rule.dValue, rule.dwFlags, 3));
}

LRESULT CFindTaskExpressionListCtrl::OnTimeUnitsChange(WPARAM /*wp*/, LPARAM /*lp*/)
{
	// update the rule 
	int nRow = GetCurSel();
	SEARCHPARAM& rule = m_aSearchParams[nRow];

	rule.dwFlags = m_eTime.GetUnits();
	SetItemText(nRow, VALUE, CTimeHelper::FormatTime(rule.dValue, rule.dwFlags, 3));

	return 0L;
}

void CFindTaskExpressionListCtrl::OnSize(UINT nType, int cx, int cy) 
{
	CInputListCtrl::OnSize(nType, cx, cy);
	
	// resize columns by proportion
	SetColumnWidth(ATTRIB, (int)(cx * COL_PROPORTIONS[ATTRIB]));
	SetColumnWidth(OPERATOR, (int)(cx * COL_PROPORTIONS[OPERATOR]));
	SetColumnWidth(VALUE, (int)(cx * COL_PROPORTIONS[VALUE]));
	SetColumnWidth(ANDOR, (int)(cx * COL_PROPORTIONS[ANDOR]));
}

void CFindTaskExpressionListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// if the user typed an alphanumeric char then begin editing automatically
	// numeric keys only work for value column
	int nItem, nCol;
	GetCurSel(nItem, nCol);
	
	if (isalpha(nChar) || (nCol == VALUE && isdigit(nChar)))
	{
		// forward key down on to edit control
		CWnd* pEdit = GetEditControl(nItem, nCol);

		if (pEdit)
		{
			EditCell(nItem, nCol);
			pEdit->PostMessage(WM_KEYDOWN, nChar, MAKELPARAM(nRepCnt, nFlags));

			return; // eat it
		}
	}
	
	CInputListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}
